<!DOCTYPE html>
<html lang='en'><head>
  <title>进程间的通信 | 江小凡的博客</title>
  <meta charset='utf-8'>
  <meta name="generator" content="Hugo 0.76.5" />
  <meta name = 'viewport' content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>
  <meta http-equiv = 'X-UA-Compatible' content = 'IE=edge'>
<meta property = 'og:locale' content = 'en_US' />
<meta property="og:type" content="article">
<meta property = 'og:title' content = '进程间的通信' />
<meta name="description" content="进程间的通信方式  信号 管道 信号量 共享内存 消息队列 套接字  对比：
   方式 传输的信息量 使用场景 关键词     信号 少量 任何 硬件来源、软件来源 / 信号队列   管道 大量 亲缘进程间 单向流动 / 内核缓冲区 / 循环队列 / 没有格式的字节流 / 操作系统负责同步   命名管道 大量 任何  …">
<meta property = 'og:description' content = '进程间的通信方式  信号 管道 信号量 共享内存 消息队列 套接字  对比：
   方式 传输的信息量 使用场景 关键词     信号 少量 任何 硬件来源、软件来源 / 信号队列   管道 大量 亲缘进程间 单向流动 / 内核缓冲区 / 循环队列 / 没有格式的字节流 / 操作系统负责同步   命名管道 大量 任何  …'>
<meta property = 'og:url' content = 'https://jxiaof.com/post/%E8%BF%9B%E7%A8%8B%E9%97%B4%E7%9A%84%E9%80%9A%E4%BF%A1/' />
<meta property = 'og:image' content = 'https://jxiaof.com/images/light.jpg'/>
<meta name = 'twitter:card' content = 'summary_large_image' />
<meta name = 'twitter:creator' content = ''>
<meta name = 'twitter:title' content = '进程间的通信' />
<meta property = 'twitter:description'  content = '进程间的通信方式  信号 管道 信号量 共享内存 消息队列 套接字  对比：
   方式 传输的信息量 使用场景 关键词     信号 少量 任何 硬件来源、软件来源 / 信号队列   管道 大量 亲缘进程间 单向流动 / 内核缓冲区 / 循环队列 / 没有格式的字节流 / 操作系统负责同步   命名管道 大量 任何  …'/>
<meta name = 'twitter:image' content = 'https://jxiaof.com/images/light.jpg' />
<link rel='apple-touch-icon' sizes='180x180' href='https://jxiaof.com/images/icons/apple-touch-icon.png'>
<link rel='icon' type='image/png' sizes='32x32' href='https://jxiaof.com/images/icons/favicon-32x32.png'>
<link rel='icon' type='image/png' sizes='16x16' href='https://jxiaof.com/images/icons/favicon-16x16.png'>
<link rel='manifest' href='https://jxiaof.com/images/icons/site.webmanifest'>

  <link rel='canonical' href='https://jxiaof.com/post/%E8%BF%9B%E7%A8%8B%E9%97%B4%E7%9A%84%E9%80%9A%E4%BF%A1/'>
  <link rel = 'stylesheet' href = 'https://jxiaof.com/css/styles.b934c9412cc837b60543673c5f879b59569b1c7bea59b56896858a3b0a15220c8dbd91e80317b591a1857e31aa26b3bf8fbaca17c5bab12ff1231a272cd6bd6d.css' integrity = 'sha512-uTTJQSzIN7YFQ2c8X4ebWVabHHvqWbVoloWKOwoVIgyNvZHoAxe1kaGFfjGqJrO/j7rKF8W6sS/xIxonLNa9bQ=='>
</head>

  <body><div class = 'nav-drop'>
  <div class = 'nav-body'>
      <a href = 'https://jxiaof.com/about/' class = 'nav_item'>About</a>
    <div class = 'nav-close'></div>
  </div>
</div><header class = 'nav' >
  <nav class = 'nav-menu'>
    <a href='https://jxiaof.com/' class = 'nav-brand nav_item'>江小凡的博客</a>
    <div class = 'nav_bar-wrap'>
      <div class = 'nav_bar'></div>
    </div>
  </nav>
</header>


    <main>
<section class = 'post_header' style = 'background-image:url(https://jxiaof.com/images/light.jpg);'>
  <h1 class='post_title'>进程间的通信</h1>
</section>
<div class = 'post'>
  <article class='post_content'><h2 id="进程间的通信方式">进程间的通信方式</h2>
<ul>
<li>信号</li>
<li>管道</li>
<li>信号量</li>
<li>共享内存</li>
<li>消息队列</li>
<li>套接字</li>
</ul>
<p>对比：</p>
<table>
<thead>
<tr>
<th>方式</th>
<th>传输的信息量</th>
<th>使用场景</th>
<th>关键词</th>
</tr>
</thead>
<tbody>
<tr>
<td>信号</td>
<td>少量</td>
<td>任何</td>
<td>硬件来源、软件来源 / 信号队列</td>
</tr>
<tr>
<td>管道</td>
<td>大量</td>
<td>亲缘进程间</td>
<td>单向流动 / 内核缓冲区 / 循环队列 / 没有格式的字节流 / 操作系统负责同步</td>
</tr>
<tr>
<td>命名管道</td>
<td>大量</td>
<td>任何</td>
<td>磁盘文件 / 访问权限 / 无数据块 / 内核缓冲区 / 操作系统负责同步</td>
</tr>
<tr>
<td>信号量</td>
<td>N</td>
<td>任何</td>
<td>互斥同步 / 原子性 / P 减 V 增</td>
</tr>
<tr>
<td>共享内存</td>
<td>大量</td>
<td>多个进程</td>
<td>内存映射 / 简单快速 / 操作系统不保证同步</td>
</tr>
<tr>
<td>消息队列</td>
<td>比信号多，但有限制</td>
<td>任何</td>
<td>有格式 / 按消息类型过滤 / 操作系统负责同步</td>
</tr>
<tr>
<td>套接字</td>
<td>大量</td>
<td>不同主机的进程</td>
<td>读缓存区 / 写缓冲区 / 操作系统负责同步</td>
</tr>
</tbody>
</table>
<h2 id="信号-signal">信号 Signal</h2>
<p>信号是 Linux 系统响应某些条件而产生的一个事件，由操作系统事先定义，接收到该信号的进程可以采取自定义的行为。这是一种“订阅-发布”的模式。</p>
<p>信号来源分为硬件来源和软件来源。</p>
<ol>
<li>硬件来源。如按下 CTRL+C、除 0、非法内存访问等等</li>
<li>软件来源。如 Kill 命令、Alarm Clock 超时、当 Reader 中止之后又向管道写数据，等等</li>
</ol>
<p>一般的信号是都是由一个错误产生的。以除 0 为例。在 x86 机器上 DIV 或 IDIV 指令除数为 0 时，会引发 0 号中断，编号 #DE(Divide Error)，即所谓除零异常。这是一个硬件级中断，会导致陷入内核，执行操作系统预定义在 IDT 中的中断处理程序。而操作系统处理这个异常的方法，就是<strong>向进程发送一个信号 <code>SIGFPE</code></strong>。如果进程设置了相应的 signal handler，就执行进程的处理方法。否则，执行操作系统的默认操作，一般这种信号的默认操作是杀死进程。</p>
<p>同理，溢出、非法内存访问（越界）、非法指令等也都属于硬件中断，由操作系统处理。<strong>操作系统会将这些硬件异常包装成“信号”发送给进程</strong>。如果进程不处理这几个异常信号，那么默认的行为就是挂掉。</p>
<p>但是，信号也可以作为进程间通信的一种方式，明确地由一个进程发送给另一个进程。</p>
<p>进程如何发送信号？</p>
<ul>
<li>操作系统提供发送信号的系统调用</li>
<li>该系统调用会将信号放到目标进程的信号队列中</li>
<li>如果目标进程未处于执行状态，则该信号就由内核保存起来，直到该进程恢复执行并传递给它为止。如果一个信号被进程设置为阻塞，则该信号的传递被延迟，直到其阻塞被取消时才被传递给进程</li>
</ul>
<p>进程如何接收信号？</p>
<ul>
<li>每个进程有一个信号队列，放其他进程发给它、等待它处理的信号</li>
<li>进程在执行过程中的特定时刻，检查并处理自己的信号队列。如从系统空间返回到用户空间之前</li>
<li>发送信号时，必须指明发送目标进程的号码。一般用在具有亲缘关系的进程之间</li>
</ul>
<p>用户进程对信号的处理过程有三种：</p>
<ol>
<li>处理信号。定义信号处理函数，当信号发生时，执行相应的处理函数</li>
<li>忽略信号。当不希望接收到的信号对进程的执行产生影响，而让进程继续执行时，可以忽略该信号，即不对信号进程作任何处理</li>
<li>不处理也不忽略。执行默认操作，linux 对每种信号都规定了默认操作</li>
</ol>
<p>有的信号，用户进程是无法处理也无法忽略的，比如<code>SIGSTOP</code>、<code>SIGKILL</code> 等。</p>
<p>关于信号的更详细的内容，可以查看<a href="https://imageslr.github.io/2020/07/09/trap-interrupt-exception.html#signal">这篇文章</a>。</p>
<h2 id="管道-pipe">管道 Pipe</h2>
<p>管道命令，在 Linux Shell 中经常使用，一般，我们使用管道操作符 <code>|</code> 来表示两个命令之间的数据通信。比如：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">ps -ef | grep java | xargs echo
</code></pre></div><p>管道操作符的内部实现其实就是 Linux 的管道接口。由管道操作符 <code>|</code> 分割的每个命令是独立的进程，各个进程的标准输出 STDOUT，会作为下一个进程的标准输入 STDIN。</p>
<h3 id="定义">定义</h3>
<p>管道是一种半双工的通信方式，数据只能<strong>单向流动</strong>，上游进程往管道中写入数据，下游进程从管道中接收数据。如果想实现双方通信，那么需要建立两个管道。</p>
<p>管道适合于<strong>传输大量信息</strong>。管道发送的内容是以字节为单位的，没有格式的<strong>字节流</strong>。</p>
<h3 id="创建管道">创建管道</h3>
<p>通过 <code>pipe()</code> 系统调用来创建并打开一个管道，当最后一个使用它的进程关闭对他的引用时，pipe 将自动撤销。</p>
<p>通过 <code>pipe()</code> 创建的是匿名管道，只能用于具有亲缘关系的进程之间（父子进程或兄弟进程）。</p>
<h3 id="管道的实现">管道的实现</h3>
<p><strong>管道就是一个文件</strong>，是一种只存在于内存中的特殊的文件系统。</p>
<p>在 Linux 中，管道借助了文件系统的 File 结构实现。父进程使用 File 结构保存向管道写入数据的例程地址，子进程保存从管道读出数据的例程地址。这解释了上文所说的：</p>
<ol>
<li>单向流动</li>
<li>只能用于具有亲缘关系的进程之间</li>
</ol>
<p>管道是由内核管理的一个缓冲区，缓冲区被设计成为环形的数据结构，以便管道可以被循环利用（循环队列）。</p>
<h3 id="管道的同步">管道的同步</h3>
<p>管道是一个具有特定大小的缓冲区</p>
<ul>
<li>操作系统会保证读写进程的同步</li>
<li>下游进程或者上游进程需要等另一方释放锁后才能操作管道。管道就相当于一个文件，同一时刻只能有一个进程访问</li>
<li>当管道为空时，下游进程读阻塞；当管道满时，上游进程写阻塞</li>
<li>管道不再被任何进程使用时，自动消失</li>
</ul>
<h2 id="命名管道-fifo">命名管道 FIFO</h2>
<p>Linux 管道包含匿名管道和命名管道。上面说的是匿名管道，只能用在亲缘进程中，管道文件信息保存在内存里。</p>
<p>命名管道（FIFO）可用于没有亲缘的进程间。Pipe 和 FIFO 除了建立、打开、删除的方式不同外，二者几乎一模一样。</p>
<p>通过 <code>mknode()</code> 系统调用或者 <code>mkfifo()</code> 函数建立命名管道。一旦建立，任何有访问权的进程都可以通过文件名将其打开和进行读写，而不局限于父子进程。</p>
<p>建立命名管道时，会在磁盘中创建一个索引节点，命名管道的名字就相当于索引节点的文件名。索引节点设置了进程的访问权限，但是没有数据块。命名管道实质上也是通过<strong>内核缓冲区</strong>来实现数据传输。有访问权限的进程，可以通过磁盘的索引节点来读写这块缓冲区。</p>
<p>当不再被任何进程使用时，命名管道在内存中释放，但磁盘节点仍然存在。</p>
<h2 id="信号量-semaphore">信号量 Semaphore</h2>
<p>信号量是一种特殊的变量，对它的操作都是原子的，有两种操作：V（<code>signal()</code>）和 P（<code>wait()</code>）。V 操作会增加信号量 S 的数值，P 操作会减少它。</p>
<ul>
<li>V(S)：如果有其他进程因等待 S 而被挂起，就让它恢复运行，否则 S 加 1</li>
<li>P(S)：如果 S 为 0，则挂起进程，否则 S 减 1</li>
</ul>
<p>P、V 来自于荷兰语：Probeer (try)、Verhoog (increment)。</p>
<p>如果信号量是一个任意的整数，通常被称为计数信号量（Counting semaphore），或一般信号量（general semaphore）；如果信号量只有二进制的 0 或 1，称为二进制信号量（binary semaphore）。在 Linux 系统中，二进制信号量又称互斥锁（Mutex）。信号量可以用于实现进程或线程的互斥和同步。</p>
<p>信号量在底层的实现是通过硬件提供的原子指令，如 <code>Test And Set</code>、<code>Compare And Swap</code>等。比如 golang 实现互斥量就是使用了 <code>Compare And Swap</code> 指令（<a href="https://github.com/golang/go/blob/master/src/sync/mutex.go#L72">github</a>）。</p>
<h2 id="共享内存-shared-memory">共享内存 Shared Memory</h2>
<p>共享内存顾名思义，允许两个或多个进程共享同一段物理内存。<strong>不同进程可以将同一段共享内存映射到自己的地址空间，然后像访问正常内存一样访问它</strong>。不同进程可以通过向共享内存端读写数据来交换信息。</p>
<p>一个进程可以通过操作系统的系统调用，创建一块共享内存区；其他进程通过系统调用把这段内存映射到自己的用户地址空间中；之后各个进程向读写正常内存一样，读写共享内存。共享内存区只会驻留在创建它的进程地址空间内。</p>
<p><strong>共享内存的优点是简单且高效</strong>，访问共享内存区域和访问进程独有的内存区域一样<strong>快</strong>，原因是不需要系统调用，不涉及用户态到内核态的转换，也不需要对数据不必要的复制。</p>
<p>比如管道和消息队列，需要在内核和用户空间进行四次的数据拷贝（读输入文件、写到管道；读管道、写到输出文件），而共享内存则只拷贝两次：一次从输入文件到共享内存区，另一次从共享内存到输出文件（<a href="https://cloud.tencent.com/developer/article/1021157">图示</a>）。此外，消息传递的实现经常采用系统调用，也就经常需要用户态和内核态互相转换；而共享内存只在建立共享内存区域时需要系统调用；一旦建立共享内存，所有访问都可作为常规内存访问，无需借助内核。</p>
<p><strong>共享内存的缺点是存在并发问题</strong>，有可能出现多个进程修改同一块内存，因此共享内存一般与信号量结合使用。</p>
<p>Linux 的 2.2.x 内核支持多种共享内存方式，如 mmap() 系统调用，Posix 共享内存，以及系统 V 共享内存。</p>
<p>mmap() 系统调用的主要作用是将<strong>普通文件</strong>映射到进程的地址空间，然后可以像访问普通内存一样对文件进行访问，不必再调用 read()，write() 等操作。mmap() 不是专门用来共享内存的，但是多个进程可以通过 mmap() 映射同一个普通文件，来实现共享内存。</p>
<p>系统 V 则是通过映射<strong>特殊文件</strong>系统 shm 中的文件实现进程间的共享内存。通过 shmget 可以创建或获得共享内存的标识符。取得共享内存标识符后，通过 shmat 将这个内存区映射到本进程的虚拟地址空间。</p>
<p>有关 mmap() 系统调用、系统 V 共享内存的详细介绍，以及两者的对比，可以进一步查看这两篇文章：</p>
<ul>
<li><a href="https://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html">Linux环境进程间通信（五）- 共享内存（上）</a></li>
<li><a href="https://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html">Linux环境进程间通信（五）- 共享内存（下）</a></li>
</ul>
<h2 id="消息队列-message-queue">消息队列 Message Queue</h2>
<p><strong>消息队列是一个消息的链表</strong>，保存在内核中。消息队列中的每个消息都是一个<strong>数据块</strong>，具有特定的格式。操作系统中可以存在多个消息队列，每个消息队列有唯一的 <strong>key</strong>，称为消息队列标识符。</p>
<p>消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。和信号相比，消息队列能够传递更多的信息。与管道相比，消息队列提供了<strong>有格式</strong>的数据，但消息队列仍然有大小限制。</p>
<p>消息队列允许一个或多个进程向它写入与读取消息。消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中，直到接收者取回它。也就是说，消息队列是异步的，但这也造成了一个缺点，就是接收者必须<strong>轮询</strong>消息队列，才能收到最近的消息。</p>
<p>操作系统提供创建消息队列、取消息、发消息等系统调用。</p>
<p>操作系统负责读写同步：若消息队列已满，则写消息进程排队等待；若取消息进程没有找到需要的消息，则在等待队列中寻找。</p>
<p>消息队列和管道相比，相同点在于二者都是通过发送-接收的方式进行通信，并且数据都有最大长度限制。不同点在于消息队列的数据是有格式的，并且<strong>取消息进程可以选择接收特定类型的消息</strong>，而不是像管道中那样默认全部接收。</p>
<h2 id="套接字-socket">套接字 Socket</h2>
<ul>
<li>不同的计算机的进程之间通过 socket 通信，<em>也可用于同一台计算机的不同进程</em></li>
<li>需要通信的进程之间首先要各自创建一个 socket，<strong>内容包括主机地址与端口号</strong>，声明自己接收来自某端口地址的数据</li>
<li>进程通过 socket 把消息发送到网络层中，网络层通过主机地址将其发到目的主机，目的主机通过端口号发给对应进程</li>
</ul>
<p>操作系统提供创建 socket、发送、接收的系统调用，为每个 socket 设置发送缓冲区、接收缓冲区。</p>
<hr>

    <div class = 'post_extra'><div class = 'copy' data-share = 'Share Story' data-copied = 'Link Copied'>
  <svg>
    <use xlink:href="#copy"></use>
  </svg>  
</div>

    </div>

  </article>
  <div class="post-comment">
       
       <h3>Comments welcome !</h3>



<div id="vcomments"></div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src='//unpkg.com/valine/dist/Valine.min.js'></script>
 <script type="text/javascript">
   new Valine({
       el: '#vcomments' ,
       appId: 'xb5Xl8O60fXEK4d13KsazkkL-gzGzoHsz',
       appKey: 'qyztiJ2tgf6AhvsrH088N29o',
       notify: 'false', 
       verify: 'false', 
       avatar:'mm', 
       placeholder: '说说你的看法吧...',
       visitor: 'true'
   });
 </script>
  </div>
  <aside><h3></h3>
<ul class='posts aside'>
<li class = 'post_item'>
  <a class = 'post_card' href='https://jxiaof.com/post/first/' title = 'ABOUT MYSELF' style = 'background-image: url(https://jxiaof.com/images/speakers.jpg);'>
  </a>
  <div class = 'excerpt'>
    <div class = 'excerpt_meta'><div class = 'copy' data-share = 'Share Story' data-copied = 'Link Copied'>
  <svg>
    <use xlink:href="#copy"></use>
  </svg>  
</div>

    </div>
    <h3 class = 'post_link'>
      <a href='https://jxiaof.com/post/first/'>ABOUT MYSELF</a>
    </h3>
    <p class = 'pale'><p>​        I am a light game enthusiast with poor gastrointestinal function. I go to bed …</p></p>
  </div>
</li>

<li class = 'post_item'>
  <a class = 'post_card' href='https://jxiaof.com/post/leetcode/' title = 'Leetcode' style = 'background-image: url(https://jxiaof.com/images/programmer.png);'>
  </a>
  <div class = 'excerpt'>
    <div class = 'excerpt_meta'><div class = 'copy' data-share = 'Share Story' data-copied = 'Link Copied'>
  <svg>
    <use xlink:href="#copy"></use>
  </svg>  
</div>

    </div>
    <h3 class = 'post_link'>
      <a href='https://jxiaof.com/post/leetcode/'>Leetcode</a>
    </h3>
    <p class = 'pale'>买卖股票简答:
class Solution: def maxProfit(self, prices): &#34;&#34;&#34; :type prices: …</p>
  </div>
</li>

</ul>

  </aside>
</div>
<script src = 'https://jxiaof.com/js/autosize.min.js'></script>
<script src = 'https://jxiaof.com/js/timeago.js'></script>
    </main><svg width="0" height="0" class="hidden">
  <symbol viewBox="0 0 699.428 699.428" xmlns="http://www.w3.org/2000/svg" id="copy">
    <path d="M502.714 0H240.428C194.178 0 153 42.425 153 87.429l-25.267.59c-46.228 0-84.019 41.834-84.019 86.838V612c0 45.004 41.179 87.428 87.429 87.428H459c46.249 0 87.428-42.424 87.428-87.428h21.857c46.25 0 87.429-42.424 87.429-87.428v-349.19zM459 655.715H131.143c-22.95 0-43.714-21.441-43.714-43.715V174.857c0-22.272 18.688-42.993 41.638-42.993l23.933-.721v393.429C153 569.576 194.178 612 240.428 612h262.286c0 22.273-20.765 43.715-43.714 43.715zm153-131.143c0 22.271-20.765 43.713-43.715 43.713H240.428c-22.95 0-43.714-21.441-43.714-43.713V87.429c0-22.272 20.764-43.714 43.714-43.714H459c-.351 50.337 0 87.975 0 87.975 0 45.419 40.872 86.882 87.428 86.882H612zm-65.572-349.715c-23.277 0-43.714-42.293-43.714-64.981V44.348L612 174.857zm-43.714 131.537H306c-12.065 0-21.857 9.77-21.857 21.835s9.792 21.835 21.857 21.835h196.714c12.065 0 21.857-9.771 21.857-21.835 0-12.065-9.792-21.835-21.857-21.835zm0 109.176H306c-12.065 0-21.857 9.77-21.857 21.834 0 12.066 9.792 21.836 21.857 21.836h196.714c12.065 0 21.857-9.77 21.857-21.836 0-12.064-9.792-21.834-21.857-21.834z"
    ></path>
  </symbol>
  <symbol viewBox="0 0 60.015 60.015" xmlns="http://www.w3.org/2000/svg" id="reply">
    <path d="M42.007 0h-24c-9.925 0-18 8.075-18 18v14c0 9.59 7.538 17.452 17 17.973v8.344a1.694 1.694 0 0 0 1.699 1.698c.44 0 .873-.173 1.198-.498l1.876-1.876C26.708 52.713 33.259 50 40.227 50h1.78c9.925 0 18-8.075 18-18V18c0-9.925-8.075-18-18-18zm16 32c0 8.822-7.178 16-16 16h-1.78c-7.502 0-14.556 2.921-19.86 8.226l-1.359 1.359V44a1 1 0 1 0-2 0v3.949c-8.356-.52-15-7.465-15-15.949V18c0-8.822 7.178-16 16-16h24c8.822 0 16 7.178 16 16v14z"></path>
  </symbol>
</svg>
<footer class = 'footer'>
  <div class = 'footer_inner wrap pale'>
    <p>&copy;&nbsp;<span class = 'year'></span>&nbsp;江小凡的博客.
    Designed by  <a href = 'https://www.github.com/jxiaof' title = 'Linkedin Profile'>江小凡</a></p>
  </div>
</footer>
<script src = 'https://jxiaof.com/js/index.min.c3b0a769e56310d4359f30762ab615db8cd6ce33d555ee7f68d25e8a40c63ce18bf6b273d35a9b5c033a7f6e71b1d424e74d9f542d93482215c671b3f31f885f.js'></script>

  </body>
</html>
